﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OneSchema
{
    public class BitSet<T> : IEnumerable<T>
    {
        // Universal set
        protected UniversalBitSet<T> m_universalSet;

        // Bit representation set
        private UInt64 m_bits;

        // Constructor
        private BitSet() { }

        public BitSet(UniversalBitSet<T> allElements, IEnumerable<T> elements)
            : this(allElements)
        {
            // Add all elements
            foreach (T e in elements)
                Add(e);
        }

        public BitSet(UniversalBitSet<T> allElements)
        {
            if (allElements == null)
                throw new UniversalSetNotDefinedException();

            m_universalSet = allElements;
        }

        public BitSet(BitSet<T> otherSet)
        {
            m_bits = otherSet.m_bits;
            m_universalSet = otherSet.m_universalSet;
        }

        public void Add(T item)
        {
            m_bits |= m_universalSet.BitPattern(item);
        }

        public void Minus(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            m_bits &= ~other.m_bits;
        }

        public void IntersectWith(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            m_bits &= other.m_bits;
        }

        public bool IsSubsetOf(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            return ((m_bits & other.m_bits) == m_bits);
        }

        public bool IsSupersetOf(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            return other.IsSubsetOf(this);
        }

        public bool SetEquals(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            return m_bits == other.m_bits;
        }

        public void UnionWith(BitSet<T> other)
        {
            if (other.m_universalSet != m_universalSet)
                throw new UniversalSetNotEqualsException();
            m_bits |= other.m_bits;
        }

        public void Complement()
        {
            m_bits = ~m_bits;
        }

        public void Clear()
        {
            m_bits = 0x0;
        }

        public bool Contains(T item)
        {
            UInt64 mask = m_universalSet.BitPattern(item);
            return ((m_bits & mask) == mask);
        }

        public bool isEmpty()
        {
            // only need to check bits that are used?
            return m_bits == 0UL;
        }

        public int Count
        {
            get 
            {
                int count = 0;
                UInt64 temp = m_bits;
                for (int i = 0; i < m_universalSet.Elements.Count; i++)
                {
                    if (temp % 2 == 1) count++;
                    temp >>= 1;
                }
                return count;
            }
        }

        public void Remove(T item)
        {
            m_bits &= ~m_universalSet.BitPattern(item);
        }

        public UInt64 BitPattern
        {
            set { m_bits = value; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            uint mask = 0x1;
            for (int i = 0; i < m_universalSet.Elements.Count; i++)
            {
                if ((m_bits & mask) == mask)
                    yield return m_universalSet.Elements[i];
                mask <<= 1;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder("{");
            foreach (T item in this)
            {
                sb.Append(item.ToString());
                sb.Append(",");
            }
            //remove last comma
            if (sb[sb.Length - 1] == ',')
                sb.Remove(sb.Length - 1, 1);

            sb.Append("}");
            return sb.ToString();
        }
    }
}